/**
 * Functions.js
 **/

/*RUNTIME LOOP*/
/************************************************************************************/
if (!window.requestAnimationFrame) {
	window.requestAnimationFrame = (function() {
		return window.webkitRequestAnimationFrame ||
				window.mozRequestAnimationFrame ||
				window.oRequestAnimationFrame ||
				window.msRequestAnimationFrame ||
				function (callback, elements) {
					window.setTimeout(callback, 1000/40);
				}
	})();
}

if (!Function.prototype.bind) {
  Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      // closest thing possible to the ECMAScript 5 internal IsCallable function
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(this instanceof fNOP && oThis
                                 ? this
                                 : oThis,
                               aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
  };
}

/*DEFINE ALIASES*/
/**********************************************************************************/
// querySelectorAll shortcut
function $(selector) {
	var qsa = document.querySelectorAll(selector);
	return (qsa.length === 1 ? qsa[0] : qsa);
}
// console.log shortcut
function log(m) {return console.log(m);}
// console.warn shortcut
function warn(m) {return console.warn(m);}
// log a todo
function todo(m) {return console.debug("[TODO] : "+m);}

/*CLASS MANIPULATION*/
/************************************************************************************/
// Check if element e has class c
function hasClass(e,c) {return e.className.match(new RegExp('(\\s|^)'+c+'(\\s|$)'));}
// Add class c to element e
function addClass(e,c) {
	if (e.length === undefined)
		e = [e];
	for (x in e) 
		if (!isNaN(x))
			if (!this.hasClass(e[x],c)) e[x].className += " "+c;
}
// Remove classe c from element e
function removeClass(e,c) {
	if (e.length === undefined)
		e = [e];
	for (x in e)
		if (!isNaN(x))
			if (hasClass(e[x],c))
				e[x].className=e[x].className.replace(new RegExp('(\\s|^)'+c+'(\\s|$)'),' ');
}

/*FORM MANIPULATION*/
/************************************************************************************/
// Get selected option from a select input
function getSelected(s) {return s.options[s.selectedIndex];}

/*DEBUG CONSOLE*/
/**********************************************************************************/
// Print something in the HTML
function debug(message) {
	var line = document.createElement('p');
	line.textContent = message;
	$('#debug').childNodes[1].appendChild(line);
}

/*IM MANAGER*/
/************************************************************************************/
// Get name from filename (eg. image.png > image)
function getIMName(src) {return src.split('.')[0];}

/*ARRAY*/
/************************************************************************************/
// Pickup a random value into an Array
Array.prototype.pickup = Array.prototype.pickup || function(){return this[Math.floor(Math.random()*this.length)];}
// Array Remove - By John Resig (MIT Licensed)
Array.prototype.remove = function(from, to) {
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};
Array.prototype.contains = Array.prototype.contains || function(value) {
    for (var i = 0; i < this.length; i++) {
        if (this[i] === value) {
            return true;
        }
    }
    return false;
}

/*OBJECT*/
/************************************************************************************/
// Convert object properties in a simple string
function o2s(obj) {
	var ret = [];
	for(var pname in obj) {
		ret.push(pname);
	}
	return '{'+ret.join(', ')+'}';
}
// Check if an object has a property
// Return property if exist, fallback else
Object.prototype.has = Object.prototype.has || function(property, fallback) {
	return this.hasOwnProperty(property) ? this[property] : fallback;
}
// Print informations about object's properties
Object.prototype.help = Object.prototype.help || function(type) {
	var properties = [];

	for(var propertyName in this) {
		var propertyType = typeof this[propertyName];
		if(typeof properties[propertyType] == "undefined") {
			properties[propertyType] = [];
		}
		properties[propertyType].push(propertyName);
	}

	if(typeof type != "undefined") {
		type = type.toLowerCase();
		if(typeof properties[type] != "undefined") {
			log("Object has "+properties[type].length+" properties of type : "+type);
			for(var iP = 0, cP = properties[type].length; iP < cP; iP++) {
				log(" - "+properties[type][iP]);
			}
		}
		else {
			warn("There is no property of type : "+type);
		}
	}
	else {
		for(var propertyType in properties) {
			if(typeof properties[propertyType] != "object") {
				continue;
			}
			log("Object has "+properties[propertyType].length+" properties of type : "+propertyType);
			for(var iP = 0, cP = properties[propertyType].length; iP < cP; iP++) {
				log(" - "+properties[propertyType][iP]);
			}
		}
	}
}
// Get configuration for an objet
Object.prototype.configure = Object.prototype.configure || function(conf) {
	// Configure object according to configuration parameter
	for(property in conf) {
		var hasDefault = this._ !== undefined;
		// Exclude functions
		if(typeof conf[property] !== "function") {
			// Set a current property
			this[property] = conf[property];
			if(hasDefault === true && this._.contains(property) === true) {
				// Set a default property
				this["_"+property] = conf[property];
			}
		}
	}
}
// Check if f is a function
function isFunction(f) {
	var getType = {};
	return f && getType.toString.call(f) === '[object Function]';
}
// Get memory used by an object
function roughSizeOfObject(object) {

    var objectList = [];
    var stack = [ object ];
    var bytes = 0;

    while ( stack.length ) {
        var value = stack.pop();

        if ( typeof value === 'boolean' ) {
            bytes += 4;
        }
        else if ( typeof value === 'string' ) {
            bytes += value.length * 2;
        }
        else if ( typeof value === 'number' ) {
            bytes += 8;
        }
        else if
        (
            typeof value === 'object'
            && objectList.indexOf( value ) === -1
        )
        {
            objectList.push( value );

            for( var i in value ) {
                stack.push( value[ i ] );
            }
        }
    }
    return bytes;
}
/*OBJECT PROGRAMMING*/
/************************************************************************************/
// Combine 2 objects in a new one
// Do not overwrite A's property
function sum(objA, objB) {
	var obj = {};

	for(var prop in objA) {
		obj[prop] = objA[prop];
	}
	for(var prop in objB) {
		if(obj[prop] === undefined) {
			obj[prop] = objB[prop];
		}
	}

	return obj;
}

// Object A prototype extends properties of Object B prototype
function extendPrototype(objA, objB) {
	objA.prototype = sum(objA.prototype, objB.prototype);
}

/*STRING*/
/************************************************************************************/
function ucfirst(string) { return string.charAt(0).toUpperCase() + string.slice(1);}
function s(msg) { return msg;}
function p(single, plural, count) { return count > 1 ? plural : single;}
/*NUMERIC*/
/************************************************************************************/
// Check a variable is a number
function isNumber(value) {
    if ((undefined === value) || (null === value)) {
        return false;
    }
    if (typeof value == 'number') {
        return true;
    }
    return !isNaN(value - 0);
}
// Return a random value between 'a' and 'b'
function rand(a, b) {return a + Math.random() * (b - a);}
// Return a random integer between 'a' and 'b'
function randi(a, b) {return Math.round(a + Math.random() * (b - a));}
// Check for an interval
function interval(t, i) {return (+new Date() - t) / 1000 > i;}
function clamp(value, min, max) {return value > max ? max : value < min ? min : value;}
// Converts from degrees to radians.
Math.radians = function(degrees) {return degrees * Math.PI / 180;};
// Converts from radians to degrees.
Math.degrees = function(radians) {return radians * 180 / Math.PI;};

/*PHYSICS*/
/************************************************************************************/
// Get distance from A[x,y] to B[x,y]
function distance(a, b) {return Math.sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));}
// Detect collision between two objects 'a' and 'b'
// Assuming both 'a' and 'b' have x/y/w/h properties
function collide(a, b) {return !(b.x >= a.x + a.width || b.x + b.width <= a.x || b.y >= a.y + a.height || b.y + b.height <= a.y);}
// Detect if object 'a' contains coordinates x, y
// Assuming 'a' has transform property and x, y are integers
function contains(a, x, y) {return x > a.transform.position.x && x < a.transform.position.x + a.transform.scale.x && y > a.transform.position.y && y < a.transform.position.y + a.transform.scale.y;}
// Move an object from o[x,y] to o.target[x,y]
// Assuming object has width,height,speed,target properties
function move(o) {
	// Get angle to target point
	var angle = Math.atan2(o.target.y - (o.y+o.height/2), o.target.x - (o.x+o.width/2));
	// Increment position to the target according to speed
	o.x += Math.cos(angle) * o.speed;
	o.y += Math.sin(angle) * o.speed;
}

/*HELPERS*/
function getWindowWidth() {
	var w = 0;
	if (typeof(window.innerWidth) == 'number') {
		w = window.innerWidth;
	} else {
		if (document.documentElement && document.documentElement.clientWidth) {
			w = document.documentElement.clientWidth;
		}
		else {
			if (document.body && document.body.clientWidth) {
				w = document.body.clientWidth;
			}
		}
	}
	return w;
}
function getWindowHeight() {
	var h = 0;
	if (typeof(window.innerHeight) == 'number') {
		h = window.innerHeight;
	} else {
		if (document.documentElement && document.documentElement.clientHeight) {
			h = document.documentElement.clientHeight;
		}
		else {
			if (document.body && document.body.clientHeight) {
				h = document.body.clientHeight;
			}
		}
	}
	return h;
}
function isMobileDevice() {
	var check = false;
	(function(a){if(/(android|bb\d+|meego|P5110).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
	return check; 
}
function isTabletDevice() {
	var check = false;
	(function(a){if(/ipad|P5110/i.test(a))check = true})(navigator.userAgent||navigator.vendor||window.opera);
	return check;
}

/*GRAPHICS*/
/************************************************************************************/
// Draw rectangle
function drawRect(ctx,c,x,y,w,h) {
	// Set fill color
	ctx.fillStyle = c;
	// Draw a filled rectangle
	ctx.fillRect(x, y, w, h);
}
// Draw wireframe rectangle
function drawStrokeRect(ctx,c,x,y,w,h,b) {
	// Set stroke color
	ctx.strokeStyle = c;
	ctx.lineWidth = b || 2;
	// Draw a filled rectangle
	ctx.strokeRect(x, y, w, h);
}
// Draw filled arc from [x,y], with r radius, from angle s to e
function drawArc(ctx,c,x,y,r,s,e,l,i) {

	var cl = l > 0 ? "rgba(0,0,0,.1)" : c;

	// Create stroke arc
	drawStrokeArc(ctx,cl,x,y,r,s,e,l,i);

	ctx.fillStyle = c;
	ctx.fill();
}
// Draw stroke arc from [x,y], with r radius, from angle s to e
function drawStrokeArc(ctx,c,x,y,r,s,e,l,i) {
	i = i || false;

	// Create path
	ctx.beginPath();
	ctx.arc(x, y, r, s, e, i);
	// Set graphics & draw
	ctx.lineWidth = l;
	ctx.strokeStyle = c;
	ctx.stroke();
	// Close path
	ctx.closePath();
}
// Draw an ellipse
function drawEllipse(ctx,c,x,y,w,h) {
	// Set positions
	var k = .5522848,	  // kappa
		ox = (w / 2) * k, // control point offset horizontal
		oy = (h / 2) * k, // control point offset vertical
		xe = x + w,       // x-end
		ye = y + h,       // y-end
		xm = x + w / 2,   // x-middle
		ym = y + h / 2;   // y-middle
	// Create path
	ctx.beginPath();
	ctx.moveTo(x, ym);
	ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
	ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
	ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
	ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
	ctx.closePath();
	// Set graphics & draw
	ctx.strokeStyle = c;
	ctx.stroke();
}
// Draw an ellipse starting to center
function drawEllipseByCenter(ctx,c,cx,cy,w,h) {
	drawEllipse(ctx, c, cx - w/2.0, cy - h/2.0, w, h);
}
// Draw a triangle starting to center
function drawTriangleByCenter(ctx, c, cx, cy, w, h, a, fillTriangle) {
    fillTriangle = !!fillTriangle;
    
    ctx.save();
    ctx.translate(cx, cy);

    ctx.rotate( Math.radians(a) ); // rotate by 47 degrees and convert to radians
    ctx.translate(-cx, -cy);

    ctx.strokeStyle = c;
    ctx.fillStyle = c;

    ctx.beginPath();

    A = {position : {x: cx, y: cy - h/2}};
    B = {position : {x: cx + (w/4), y: cy + h/2}};
    C = {position : {x: cx - (w/4), y: cy + h/2}};

    ctx.moveTo(A.position.x, A.position.y);
    ctx.lineTo(B.position.x, B.position.y);
    ctx.lineTo(C.position.x, C.position.y);

    ctx.closePath();

    if(fillTriangle) {
        ctx.fill();
    }
    else {
        ctx.stroke();
    }

    ctx.restore();
}
// Draw a filled text
function drawText(ctx,c,x,y,str,shadow) {

	if(shadow === true) {
		// Set fill color
		ctx.fillStyle = "rgba(0,0,0,1)";
		// Draw filled text
		ctx.fillText(str, x+1, y+1);
	}
	
	// Set fill color
	ctx.fillStyle = c;
	// Draw filled text
	ctx.fillText(str, x, y);
}
function drawTextWrap(ctx,c,x,y,str,w,l,shadow) {
	var words = str.toString().split(' ');
	var line = '',
		maxWidth = 0;

	for(var i = 0, ci = words.length; i < ci; i++) {
		var testLine = line + words[i] + ' ';
		var width = ctx.measureText(testLine).width;
		if (i > 0 && width > w) {
			drawText(ctx,c,x,y,line,shadow);
			line = words[i] + ' ';
			y += l;
		} else {
			line = testLine;
		}
	}

	drawText(ctx,c,x,y,line,shadow);
}
function drawTextWrapCenter(ctx,c,x,y,str,w,l,shadow) {
	var words = str.toString().split(' ');
	var line = '',
		maxWidth = 0;

	for(var i = 0, ci = words.length; i < ci; i++) {
		var testLine = line + words[i] + ' ';
		var width = ctx.measureText(testLine).width;
		if (i > 0 && width > w) {
			width = ctx.measureText(line).width;
			drawText(ctx,c,x - width/2,y,line,shadow);
			line = words[i] + ' ';
			y += l;
		} else {
			line = testLine;
		}
	}

	width = ctx.measureText(line).width;
	drawText(ctx,c,x - width/2,y,line,shadow);
}
function drawTextWrapForButton(ctx,c,x,y,str,w,l,shadow) {
	var line  = "",
		lines = []
		width = 0,
		words = str.toString().split(" ");

	for(var i = 0, ci = words.length; i < ci; i++) {
		var _line = line + words[i] + " ";

		if(ctx.measureText(_line).width > w) {
			lines.push(line);
			line = words[i] + " ";
		} else {
			line = _line;
		}

		var _width = ctx.measureText(line).width;
		if(_width > width) {
			width = _width;
		}
	}

	lines.push(line);

	y = y + Math.floor(ctx.font.match(/[0-9]*/i)[0]/2) - Math.floor(ctx.font.match(/[0-9]*/i)[0]/2) * lines.length + 4;

	for(var j = 0, cj = lines.length; j < cj; j++) {
		var _x = x + Math.round(w/2) - width / 2;
		var _y = y + (l * j);
		drawText(ctx,c,_x,_y,lines[j],shadow);
	}
}
// Draw a rect button with text
function drawRectButton(ctx,cr,x,y,w,h,str,ct,ft,st) {
	// Draw a rect
	drawRect(ctx, cr, x, y, w, h);
	// Get pixel length of the text
	var len = ctx.measureText(str);
	// Get center position in the rectangle
	y = y + Math.round(h/2);
	// Draw text at center
	var font = ctx.font;
	ctx.font = ft || font;
	drawTextWrapForButton(ctx,ct,x,y,str,w,12,st);
	ctx.font = font;
}
// Draw a line between 2 points
function drawLine(ctx,c,ax,ay,bx,by,b) {
	ctx.lineWidth = b || 1;
	// Set stroke color
	ctx.strokeStyle = c;
	// Start path
    ctx.beginPath();
    // Move to origin point
    ctx.moveTo(ax, ay);
    // Trace to destination point
    ctx.lineTo(bx, by);
    // Draw line
    ctx.stroke();
}

function drawLosange(ctx, cf, cs, x, y, w, h) {

	ctx.save();
    ctx.translate(0, h/2);

    // Draw part up
	drawTriangleByCenter(ctx, cf, x, y-h/4, w*2, h/2, 0, true);
	// Draw part down
	drawTriangleByCenter(ctx, cf, x, y+h/2, w*2, h, 180, true);

	// Draw line to fill between parts
	drawLine(ctx, cf, x-w/2+1, y, x+w/2-2, y);

	var a = {x:x, y:y-h/2},
		b = {x:x+w/2, y:y};

	// Top
	drawLine(ctx, cs, a.x, a.y, b.x, b.y);

	a.x = b.x;
	a.y = b.y;

	b = {x:x,y:y+h};

	// Right
	drawLine(ctx, cs, a.x, a.y, b.x, b.y);

	a.x = b.x;
	a.y = b.y;

	b = {x:x-w/2,y:y};

	// Bottom
	drawLine(ctx, cs, a.x, a.y, b.x, b.y);

	a.x = b.x;
	a.y = b.y;

	b = {x:x,y:y-h/2};

	// Left
	drawLine(ctx, cs, a.x, a.y, b.x, b.y);

	ctx.restore();
}

function gradient(ca, cb, percentage) {

	var distance = colorDistance(ca, cb);
	var increment = calculateIncrement(distance, percentage);

	if (ca[0] > cb[0]) {
        ca[0] -= increment[0];
    } else {
        ca[0] += increment[0];
    }
    
    if (ca[1] > cb[1]) {
        ca[1] -= increment[1];
    } else {
        ca[1] += increment[1];
    }
    
    if (ca[2] > cb[2]) {
        ca[2] -= increment[2];
    } else {
        ca[2] += increment[2];
    }

	return ca;
}

function colorDistance(ca, cb) {
    var distance = [];
    for (var i = 0; i < 3; i++) {
        distance.push(Math.abs(ca[i] - cb[i]));
    }
    return distance;
}

function calculateIncrement(distance,percentage) {
    var increment = [];
    for (var i = 0; i < 3; i++) {
        increment.push(Math.abs(Math.floor(distance[i] * percentage / 100)));
        if (increment[i] == 0) {
            increment[i]++;
        }
    }
    return increment;
}


function global_animate(element, property_to_map, duration, callback) {
   duration = duration || 1000;                    
   var delta = function (a) { return a; };          
   var start = new Date().getTime();                
   var property_from_map = {};                      
   var property_units_map = {};                    

   for (var property in property_to_map) {         
        property_from_map[property] = window.getComputedStyle(element, null)[property] || element.currentStyle[property];
        property_units_map[property] = parseUnits(property_from_map[property]);
        property_from_map[property] = parseInt(property_from_map[property]);
        property_to_map[property] = parseInt(property_to_map[property]);
    }

    setTimeout(function() {
        var now = (new Date().getTime()) - start;    
        var progress = now / duration;              

        for (var property in property_to_map) {
            var result = (property_to_map[property] - property_from_map[property]) * delta(progress) + property_from_map[property];
            element.style[property] = result.toFixed(2) + 'px';
        }

        if (progress < 1)                           
            setTimeout(arguments.callee, 10);
        else
            if (typeof callback == 'function')
                callback();
    }, 10);
}

function parseUnits(a) {
    try {
        return a.replace(/[^-\d\.]/g, '');
    } catch (e) {
        return "";
    }                    
}

function percent(a, b) {
	return a * b / 100;
}

function alpha(that, ctx, alpha, method) {
	var globalAlpha = ctx.globalAlpha;

	ctx.globalAlpha = alpha/100;

	method(that);	

	ctx.globalAlpha = globalAlpha;
}